package com.fakesmoker.aidawnda.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fakesmoker.aidawnda.common.ErrorCode;
import com.fakesmoker.aidawnda.mapper.UserMapper;
import com.fakesmoker.aidawnda.model.dto.user.UserQueryRequest;
import com.fakesmoker.aidawnda.model.entity.User;
import com.fakesmoker.aidawnda.utils.AlgorithmUtils;
import com.fakesmoker.aidawnda.utils.SqlUtils;
import com.fakesmoker.aidawnda.constant.CommonConstant;
import com.fakesmoker.aidawnda.exception.BusinessException;
import com.fakesmoker.aidawnda.model.enums.UserRoleEnum;
import com.fakesmoker.aidawnda.model.vo.LoginUserVO;
import com.fakesmoker.aidawnda.model.vo.UserVO;
import com.fakesmoker.aidawnda.service.UserService;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import javafx.util.Pair;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBitSet;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import static com.fakesmoker.aidawnda.constant.UserConstant.*;

/**
 * 用户服务实现
 *
 * @author lcy
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {


    /**
     * 盐值，混淆密码
     */
    public static final String SALT = "yupi";

    String LOGIN_KEY_PREFIX = "login:";
    // 月份格式化器
    private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyyMM");
    // 年份格式化器
    private static final DateTimeFormatter YEAR_FORMATTER = DateTimeFormatter.ofPattern("yyyy");

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient; // Redisson客户端

    // BitMap 的 Redis Key 前缀
    private static final String USER_PROBLEM_RECORD_PREFIX = "user:problem:record:";


    /**
     * 记录用户每日刷题状态
     *
     * @param userId 用户ID
     * @param date   日期
     * @param solved 是否完成刷题
     */
    public void recordDailyProblemStatus(Long userId, LocalDate date, boolean solved) {
        // 生成 BitMap 的 Redis Key
        String key = USER_PROBLEM_RECORD_PREFIX + userId + ":" + YEAR_FORMATTER.format(date);

        // 获取 BitSet（通过 Redisson）
        RBitSet bitSet = redissonClient.getBitSet(key);

        // 计算当前日期在一年中的第几天（偏移量）
        int dayOfYear = date.getDayOfYear() - 1; // BitSet 从 0 开始

        // 设置 BitMap 的值
        bitSet.set(dayOfYear, solved);

        // 设置 Key 的过期时间为 1 年 + 1 天（避免跨年问题）
        bitSet.expire(365 + 1, TimeUnit.DAYS);
        HashMap hashMap = new HashMap();
        hashMap.put(null,key);
    }

    /**
     * 获取用户年度刷题总天数
     *
     * @param userId 用户ID
     * @param year   年份
     * @return 刷题总天数
     */
    public int getAnnualProblemSolvedDays(Long userId, int year) {
        String key = USER_PROBLEM_RECORD_PREFIX + userId + ":" + year;

        // 获取 BitSet
        RBitSet bitSet = redissonClient.getBitSet(key);

        // 统计 BitMap 中为 true 的位数
        return (int) bitSet.cardinality();
    }

    /**
     * 获取用户某月刷题总天数
     *
     * @param userId 用户ID
     * @param year   年份
     * @param month  月份
     * @return 刷题总天数
     */
    public int getMonthlyProblemSolvedDays(Long userId, int year, int month) {
        String key = USER_PROBLEM_RECORD_PREFIX + userId + ":" + year;

        // 获取 BitSet
        RBitSet bitSet = redissonClient.getBitSet(key);

        // 计算该月的起始和结束偏移量
        LocalDate startDate = LocalDate.of(year, month, 1);
        LocalDate endDate = startDate.plusMonths(1).minusDays(1);

        int startDayOfYear = startDate.getDayOfYear() - 1;
        int endDayOfYear = endDate.getDayOfYear() - 1;

        // 统计该月范围内的刷题天数
        int count = 0;
        for (int i = startDayOfYear; i <= endDayOfYear; i++) {
            if (bitSet.get(i)) {
                count++;
            }
        }
        return count;
    }

    /**
     * 获取用户连续刷题天数
     *
     * @param userId 用户ID
     * @param year   年份
     * @return 连续刷题天数
     */
    public int getContinuousProblemSolvedDays(Long userId, int year) {
        String key = USER_PROBLEM_RECORD_PREFIX + userId + ":" + year;

        // 获取 BitSet
        RBitSet bitSet = redissonClient.getBitSet(key);

        int maxContinuousDays = 0;
        int currentContinuousDays = 0;

        // 遍历一年中的所有天数
        for (int i = 0; i < 365; i++) {
            if (bitSet.get(i)) {
                currentContinuousDays++;
                maxContinuousDays = Math.max(maxContinuousDays, currentContinuousDays);
            } else {
                currentContinuousDays = 0;
            }
        }
        return maxContinuousDays;
    }






    /**
     * 获取月度活跃用户统计数据
     *
     * @return 月度活跃用户统计数据
     */
    public BitSet getMonthlyActiveUsers() {

        // 获取存储月度活跃用户信息的键
        String monthlyKey = getMonthlyKey();
        // 从Redis中获取位图数据
        return retrieveBitSet(monthlyKey);
    }

    /**
     * 获取年度活跃用户统计数据
     *
     * @return 年度活跃用户统计数据
     */
    public BitSet getYearlyActiveUsers() {
        // 获取存储年度活跃用户信息的键
        String yearlyKey = getYearlyKey();
        // 从Redis中获取位图数据
        return retrieveBitSet(yearlyKey);
    }

    /**
     * 获取存储月度活跃用户信息的键
     */
    private String getMonthlyKey() {
        LocalDate today = LocalDate.now();
        String monthKey = today.format(MONTH_FORMATTER);
        return LOGIN_KEY_PREFIX + "monthly:" + monthKey;
    }

    /**
     * 获取存储年度活跃用户信息的键
     */
    private String getYearlyKey() {
        LocalDate today = LocalDate.now();
        String yearKey = today.format(YEAR_FORMATTER);
        return LOGIN_KEY_PREFIX + "yearly:" + yearKey;
    }

    /**
     * 从Redis中获取位图数据
     */
    private BitSet retrieveBitSet(String key) {

        // 获取存储在Redis中的位图数据
        byte[] bytes = (byte[]) redisTemplate.opsForValue().get(key);
        if (bytes != null) {
            // 将字节数组转换为BitSet
            return BitSet.valueOf(bytes);
        } else {
            // 若不存在，则返回空的BitSet
            return new BitSet();
        }
    }

    // 获取月度活跃用户统计数据
    public Integer getMonthlyActiveUserCount() {
        BitSet monthlyActiveUsers = getMonthlyActiveUsers();
        return monthlyActiveUsers.cardinality();
    }

    // 统计年度活跃用户数量
    public Integer getYearlyActiveUserCount() {
        BitSet yearlyActiveUsers = getYearlyActiveUsers();
        return yearlyActiveUsers.cardinality();
    }

    // 判断某个用户是否为月度活跃用户
    String userId = "user123";

    public boolean isMonthlyActiveUser(Long userId) {
        BitSet monthlyActiveUsers = getMonthlyActiveUsers();
        return monthlyActiveUsers.get(getUserIdHashCode(userId));
    }

    // 判断某个用户是否为年度活跃用户
    public boolean isYearlyActiveUser(Long userId) {
        BitSet yearlyActiveUsers = getYearlyActiveUsers();
        return yearlyActiveUsers.get(getUserIdHashCode(userId));
    }



    /**
     * 记录用户登录
     *
     * @param userId 用户ID
     */
    public void recordLogin(Long userId) {

        // 获取存储当天用户登录信息的键
        String loginKey = getLoginKey();
        // 每个用户对应一个位 ,计算位图偏移量，对应用户ID的哈希码
        int bitOffset = getUserIdHashCode(userId);
        // 将用户ID对应的位设置为1，表示用户登录
        redisTemplate.opsForValue().setBit(loginKey, bitOffset, true);
    }

    /**
     * 计算用户ID的哈希码，保证非负数并适应位图长度
     */
    private int getUserIdHashCode(Long userId) {

        int hashCode = userId.hashCode();
        // 1073741823是Redis位图最大支持长度(2^30-1)，可根据实际需求调整
        return Math.abs(hashCode % 1073741823);
    }
    private String getLoginKey() {
        LocalDate today = LocalDate.now();
        String dateKey = today.format(DateTimeFormatter.ISO_DATE);
        return USER_LOGIN_ID + dateKey;
    }


    @Override
    public long userRegister(String userAccount, String userPassword, String checkPassword) {
        // 1. 校验
        if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
        }
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短");
        }
        if (userPassword.length() < 8 || checkPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");
        }
        // 密码和校验密码相同
        if (!userPassword.equals(checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致");
        }
        synchronized (userAccount.intern()) {
            // 账户不能重复
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("userAccount", userAccount);
            long count = this.baseMapper.selectCount(queryWrapper);
            if (count > 0) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
            }
            // 2. 加密
            String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
            // 3. 插入数据
            User user = new User();
            user.setUserAccount(userAccount);
            user.setUserPassword(encryptPassword);
            boolean saveResult = this.save(user);
            if (!saveResult) {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败，数据库错误");
            }
            return user.getId();
        }
    }

    @Override
    public LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        // 1. 校验
        if (StringUtils.isAnyBlank(userAccount, userPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
        }
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号错误");
        }
        if (userPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
        }
        // 2. 加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 查询用户是否存在
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
        queryWrapper.eq("userPassword", encryptPassword);
        User user = this.baseMapper.selectOne(queryWrapper);
        // 用户不存在
        if (user == null) {
            log.info("user login failed, userAccount cannot match userPassword");
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");
        }
        // 3. 记录用户的登录态
        request.getSession().setAttribute(USER_LOGIN_STATE, user);
        request.getSession().setAttribute(USER_LOGIN_ID, user.getId());
        return this.getLoginUserVO(user);
    }

    @Override
    public List<UserVO> searchUserListsByTags(List<String> tagNameList) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        for (String tagName : tagNameList) {
            queryWrapper.like("userTag", tagName);
        }
        List<User> userList = this.list(queryWrapper);
        return userList.stream().map(this::getUserVO).collect(Collectors.toList());
    }

    @Override
    public List<UserVO> searchUsersListsByTags(List<String> tagNameList) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        if (tagNameList != null && !tagNameList.isEmpty()) {
            for (int i = 0; i < tagNameList.size(); i++) {
                String tagName = tagNameList.get(i);
                if (i == 0) {
                    queryWrapper.like("userTag", tagName);
                } else {
                    queryWrapper.or().like("userTag", tagName);
                }
            }
        }
        List<User> userList = this.list(queryWrapper);
        return userList.stream().map(this::getUserVO).collect(Collectors.toList());
    }

    /**
     * 获取当前登录用户
     *
     * @param request
     * @return
     */
    @Override
    public User getLoginUser(HttpServletRequest request) {
        // 先判断是否已登录
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User currentUser = (User) userObj;
        if (currentUser == null || currentUser.getId() == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        // 从数据库查询（追求性能的话可以注释，直接走缓存）

        long userId = currentUser.getId();
        String redisKey = USER_CACHE_KEY_PREFIX + userId;

        // 尝试从 Redis 获取用户信息
        Gson gson=new Gson();
        String userJson = (String) redisTemplate.opsForValue().get(redisKey);
        if (!StringUtils.isEmpty(userJson)) {
            // 缓存命中，直接返回
            currentUser = gson.fromJson(userJson,User.class);
        } else {
            // 缓存未命中，查询数据库并缓存
            currentUser = this.getById(userId);
            if (currentUser == null) {
                throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
            }

            // 将查询到的用户信息存入缓存，设置合理的过期时间，例如 30 分钟
            redisTemplate.opsForValue().set(redisKey, gson.toJson(currentUser), 30, TimeUnit.MINUTES);
        }

        return currentUser;


       /* long userId = currentUser.getId();
        currentUser = this.getById(userId);
        if (currentUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        return currentUser;*/
    }

    /**
     * 获取当前登录用户（允许未登录）
     *
     * @param request
     * @return
     */
    @Override
    public User getLoginUserPermitNull(HttpServletRequest request) {
        // 先判断是否已登录
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User currentUser = (User) userObj;
        if (currentUser == null || currentUser.getId() == null) {
            return null;
        }
        // 从数据库查询（追求性能的话可以注释，直接走缓存）
        long userId = currentUser.getId();
        return this.getById(userId);
    }

    /**
     * 是否为管理员
     *
     * @param request
     * @return
     */
    @Override
    public boolean isAdmin(HttpServletRequest request) {
        // 仅管理员可查询
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User user = (User) userObj;
        return isAdmin(user);
    }

    @Override
    public boolean isAdmin(User user) {
        return user != null && UserRoleEnum.ADMIN.getValue().equals(user.getUserRole());
    }

    /**
     * 用户注销
     *
     * @param request
     */
    @Override
    public boolean userLogout(HttpServletRequest request) {
        if (request.getSession().getAttribute(USER_LOGIN_STATE) == null) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录");
        }
        // 移除登录态
        request.getSession().removeAttribute(USER_LOGIN_STATE);
        return true;
    }

    @Override
    public LoginUserVO getLoginUserVO(User user) {
        if (user == null) {
            return null;
        }
        LoginUserVO loginUserVO = new LoginUserVO();
        BeanUtils.copyProperties(user, loginUserVO);
        return loginUserVO;
    }

    @Override
    public UserVO getUserVO(User user) {
        if (user == null) {
            return null;
        }
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        return userVO;
    }

    @Override
    public List<UserVO> getUserVO(List<User> userList) {
        if (CollUtil.isEmpty(userList)) {
            return new ArrayList<>();
        }
        return userList.stream().map(this::getUserVO).collect(Collectors.toList());
    }

    @Override
    public QueryWrapper<User> getQueryWrapper(UserQueryRequest userQueryRequest) {
        if (userQueryRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
        }
        Long id = userQueryRequest.getId();
        String unionId = userQueryRequest.getUnionId();
        String mpOpenId = userQueryRequest.getMpOpenId();
        String userName = userQueryRequest.getUserName();
        String userProfile = userQueryRequest.getUserProfile();
        String userRole = userQueryRequest.getUserRole();
        String sortField = userQueryRequest.getSortField();
        String sortOrder = userQueryRequest.getSortOrder();
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(id != null, "id", id);
        queryWrapper.eq(StringUtils.isNotBlank(unionId), "unionId", unionId);
        queryWrapper.eq(StringUtils.isNotBlank(mpOpenId), "mpOpenId", mpOpenId);
        queryWrapper.eq(StringUtils.isNotBlank(userRole), "userRole", userRole);
        queryWrapper.like(StringUtils.isNotBlank(userProfile), "userProfile", userProfile);
        queryWrapper.like(StringUtils.isNotBlank(userName), "userName", userName);
        queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
                sortField);
        return queryWrapper;
    }

    /**
     * 更新用户答题数
     *
     * @param userId
     */
    @Override
    public void updateUserAnswerCount(Long userId) {
        User user = getById(userId);
        //更新答题记录
        user.setUpdateTime(new Date());
        user.setTotalCount(user.getTotalCount() + 1);
        updateById(user);
    }

    /**
     * 推荐匹配用户
     *
     * @param num
     * @param loginUser
     * @return
     */
    @Override
    public List<UserVO> matchUsers(long num, User loginUser) {
//        这里我因为电脑内存问题，没有办法像fakemsoker电脑那样可以存放100万数据，可以直接运行。所以我选择了运行5万条数据。
//        不然的话会报 OOM（内存）的问题
     //   QueryWrapper<User> queryWrapper = new QueryWrapper<>();
       // List<User> userList = this.list(queryWrapper);

//         或者用page分页查询，自己输入或默认数值，但这样匹配就有限制了
        //   List<User> userList = this.page(new Page<>(pageNum, pageSize), queryWrapper);

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.isNotNull("tags");
        queryWrapper.select("id", "tags");
        queryWrapper.last("limit 50000");
        List<User> userList = this.list(queryWrapper);

        String tags = loginUser.getUserTag();
        Gson gson = new Gson();
        List<String> tagList = gson.fromJson(tags, new TypeToken<List<String>>() {
        }.getType());
        // 用户列表的下表 => 相似度'
        List<Pair<User, Long>> list = new ArrayList<>();
        // 依次计算当前用户和所有用户的相似度
        for (int i = 0; i < userList.size(); i++) {
            User user = userList.get(i);
            String userTags = user.getUserTag();
            //无标签的 或当前用户为自己
            if (StringUtils.isBlank(userTags) || user.getId() == loginUser.getId()) {
                continue;
            }
            List<String> userTagList = gson.fromJson(userTags, new TypeToken<List<String>>() {
            }.getType());
            //计算分数
            long distance = AlgorithmUtils.minDistance(tagList, userTagList);
            list.add(new Pair<>(user, distance));
        }
        //按编辑距离有小到大排序
        List<Pair<User, Long>> topUserPairList = list.stream()
                .sorted((a, b) -> (int) (a.getValue() - b.getValue()))
                .limit(num)
                .collect(Collectors.toList());
        //有顺序的userID列表
        List<Long> userListVo = topUserPairList.stream().map(pari -> pari.getKey().getId()).collect(Collectors.toList());

        //根据id查询user完整信息
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.in("id", userListVo);
        Map<Long, List<UserVO>> userIdUserListMap = this.list(userQueryWrapper).stream()
                .map(this::getUserVO)
                .collect(Collectors.groupingBy(UserVO::getId));

        // 因为上面查询打乱了顺序，这里根据上面有序的userID列表赋值
        List<UserVO> finalUserList = new ArrayList<>();
        for (Long userId : userListVo) {
            finalUserList.add(userIdUserListMap.get(userId).get(0));
        }
        return finalUserList;
    }

}
